Skip to content

refactor(organization): migrate ListByUser callers to membership, delete legacy method#1639

Merged
AmanGIT07 merged 4 commits into
mainfrom
feature/orglistbyuser-migrate-callers-to-membership-and-delete
May 22, 2026
Merged

refactor(organization): migrate ListByUser callers to membership, delete legacy method#1639
AmanGIT07 merged 4 commits into
mainfrom
feature/orglistbyuser-migrate-callers-to-membership-and-delete

Conversation

@AmanGIT07
Copy link
Copy Markdown
Contributor

@AmanGIT07 AmanGIT07 commented May 21, 2026

Summary

Migrates every reader of "orgs a principal belongs to" off organization.Service.ListByUser (which queried SpiceDB via relationService.LookupResources) onto Postgres policies via membership.Service.ListOrgsByPrincipal.

Changes

New surface

  • organization.Filter.Principal *authenticate.Principal — when set, narrows the result to orgs the principal has a policy on. When Filter.IDs is also set, the two are intersected.
  • membership.Service.ListOrgsByPrincipal(ctx, principal) — thin shim over ListResourcesByPrincipal(..., OrganizationNamespace, ResourceFilter{}). Exists so organization.MembershipService can declare it without a circular import (membership already imports organization).
  • organization.Service.List now branches on Filter.Principal: nil-checks the membership dep, calls the shim, intersects with caller-supplied IDs, short-circuits empty, falls through to the repo. Re-instruments with metrics.ServiceOprLatency(\"organization\", \"List\") (the old \"ListByUser\" histogram is gone).

Handler migrations — every site that used orgService.ListByUser now calls orgService.List(Filter{Principal: …}):

  • internal/api/v1beta1connect/authenticate.go — JWT orgs claim (excludes disabled orgs via State: organization.Enabled)
  • internal/api/v1beta1connect/user.goListOrganizationsByUser, ListOrganizationsByCurrentUser
  • internal/api/v1beta1connect/organization.goListOrganizations, ListAllOrganizations. The two were near-identical; collapsed into a searchOrgs private helper. The only response-shape diff (Count) lives in the 3-line RPC wrappers.

Internal callers — kept on the direct membership path (no enrichment needed):

  • core/deleter/service.goDeleteUser
  • core/invitation/service.goisUserOrgMember
  • core/domain/service.goListJoinableOrgsByDomain

Deletions

  • organization.Service.ListByUser
  • organization.Filter.UserID
  • The if f.UserID != \"\" branch in organization.Service.List
  • organization.RelationService.LookupResources from the interface (only ListByUser used it)
  • OrgService.ListByUser from the deleter, invitation, domain, and v1beta1connect interfaces

Deliberate behavior changes

These are intentional fixes, not regressions:

  • DeleteUser now visits disabled orgs. Old path went ListByUser → repo.List → notDisabledOrgExp, which silently skipped disabled orgs and left orphan policies pointing at the deleted user. New path reads policies directly. Verify with: delete a user who has a stale policy on a disabled org → no remaining rows in policies table referencing that user_id.
  • invitation.isUserOrgMember correctly counts stale policies on disabled orgs as membership. Prevents re-inviting a user to a disabled org they technically still have a policy on. Old ListByUser filtered disabled orgs out of the membership check, so the invite would (incorrectly) succeed and then later fail at orgService.Get → ErrDisabled.
  • domain.ListJoinableOrgsByDomain excludes disabled orgs the user has stale policies on from the joinable suggestions. Same root cause.
  • JWT orgs claim path preserves the old behavior (disabled orgs excluded) via the explicit State: organization.Enabled filter on the enrichment call.

Architecture notes

  • The Filter.Principal shape is morally the same as the deleted Filter.UserID, but defensible because: (1) typed *authenticate.Principal carries principal type and PAT context, (2) both branches read the same store (Postgres) — no hidden subsystem switch, (3) named for what it semantically does.
  • Composition lives in organization.Service.List, not the handler. Handler decodes the request, the service decides how to satisfy the query. Pattern is forward-compatible with the next two PRs (groups in CLD-3182, projects in CLD-3183) — each adds its own Filter.Principal and a matching Service.List{Resource}ByPrincipal shim on membership.
  • ID-only callers (deleter, invitation, domain) deliberately skip enrichment — they don't need org rows, just IDs, so they hit membership directly without going through orgService.List.

RPCs to verify end-to-end

  • FrontierService/ListOrganizations (with and without user_id)
  • FrontierService/ListAllOrganizations (with and without user_id)
  • FrontierService/ListOrganizationsByUser
  • FrontierService/ListOrganizationsByCurrentUser — exercise as user, service-user, and PAT; PAT must yield the intersected set
  • FrontierService/AuthToken (with auth.token.claims.add_org_ids: true) — JWT claim should exclude disabled orgs; PAT path should yield PAT-narrowed orgs
  • FrontierService/DeleteUser — verify the disabled-org-cleanup fix
  • FrontierService/CreateOrganizationInvitation — verify stale-policy-on-disabled-org now blocks re-invite

Test plan

  • make build
  • make lint (0 issues on touched packages)
  • make test -race
  • New core/domain/service_test.go covers the disabled-org-policy-holder case in ListJoinableOrgsByDomain
  • TestService_List in core/organization replaces the old TestService_ListByUser with broader pass-through coverage (empty filter, IDs, state, combined, error propagation, no-rows)
  • Updated TestConnectHandler_ListOrganizationsByUser / TestConnectHandler_ListOrganizationsByCurrentUser mock expectations to the new Filter{Principal} pattern; dropped now-unused membership-service mocks from those handlers
  • make e2e-test — regression suite around org, user, deleter, invitation, domain
  • Manual: stale-relation regression — demote an owner to viewer, confirm they no longer appear as a member via the listing APIs

Not in this PR

  • Group migration (CLD-3182) and project migration (CLD-3183) — separate PRs.

🤖 Generated with Claude Code

rohilsurana and others added 2 commits May 19, 2026 16:55
…ete legacy method

Switches every reader of "orgs a principal belongs to" off `organization.Service.ListByUser`
(SpiceDB `LookupResources` + repo enrichment) onto Postgres policy reads via
`membership.Service.ListOrgsByPrincipal`. Adds `organization.Filter.Principal` so the
composition lives inside `Service.List` instead of the handler. Five handler call sites and
three internal callers migrated; old `ListByUser`, `Filter.UserID`, and the org service's
`RelationService.LookupResources` dependency are deleted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment May 22, 2026 8:46am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5d126c7d-af92-4c46-87d6-905f83c65336

📥 Commits

Reviewing files that changed from the base of the PR and between cf9c732 and 281bb17.

📒 Files selected for processing (1)
  • core/domain/service_test.go

📝 Walkthrough

Summary by CodeRabbit

  • Refactor

    • Organization and membership checks now use principal-based resource lookups for consistent membership resolution across services.
    • Organization listing and filtering now rely on principal-based queries instead of user ID, standardizing access-control behavior.
    • User deletion and invitation flows now determine org memberships via the membership service to ensure consistent removal/validation.
  • New Features

    • When enabled, access tokens include org IDs gathered via principal-based organization listing.

Walkthrough

Replaces OrganizationService.ListByUser with MembershipService.ListResourcesByPrincipal, switches organization filters from UserID to Principal, and updates services, API handlers, mocks, and tests to use membership-based principal lookups.

Changes

Principal-based organization API refactoring

Layer / File(s) Summary
Organization filter and membership foundation
core/organization/filter.go, core/membership/service.go, core/organization/mocks/membership_service.go
Filter now uses Principal *authenticate.Principal; membership adds ListOrgsByPrincipal and related mocks updated.
Organization service refactor
core/organization/service.go, core/organization/mocks/*, core/organization/service_test.go
Removes relation-based lookup and Service.ListByUser, adds MembershipService.ListOrgsByPrincipal, and applies principal-based filtering in Service.List.
Domain service and domain mocks
core/domain/service.go, core/domain/mocks/*, core/domain/service_test.go
ListJoinableOrgsByDomain now uses membership.ListResourcesByPrincipal; interfaces and domain mocks updated and tests added.
Deleter & invitation service updates
core/deleter/service.go, core/deleter/mocks/*, core/invitation/service.go, core/invitation/mocks/*, core/deleter/service_test.go, core/invitation/service_test.go
DeleteUser and invitation membership checks use MembershipService.ListResourcesByPrincipal; mocks and tests adjusted accordingly.
API handlers and handler tests
internal/api/v1beta1connect/organization.go, internal/api/v1beta1connect/authenticate.go, internal/api/v1beta1connect/user.go, internal/api/v1beta1connect/mocks/*, internal/api/v1beta1connect/user_test.go
Handlers now build organization.Filter with Principal and use a shared searchOrgs helper; handler tests and mocks updated to expect principal-based filters.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • raystack/frontier#1618: Introduces the MembershipService.ListResourcesByPrincipal API that this PR depends on and adopts across the codebase.
  • raystack/frontier#1471: Related interface-level changes to organization service RPCs and API surface.

Suggested reviewers

  • rohilsurana
  • whoAbhishekSah
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/organization/service_test.go (1)

207-297: ⚡ Quick win

Add principal-path coverage for Service.List.

This suite no longer verifies the new Filter.Principal flow (membershipService.ListOrgsByPrincipal, ID intersection, empty short-circuit, and unwired membership error). Please add focused subtests for those branches to protect the refactor’s core behavior.

Suggested test additions
 func TestService_List(t *testing.T) {
   ctx := context.Background()

-  newService := func() (*organization.Service, *mocks.Repository) {
+  newService := func() (*organization.Service, *mocks.Repository, *mocks.MembershipService) {
     mockRepo := mocks.NewRepository(t)
+    mockMembership := mocks.NewMembershipService(t)
     ...
     svc := organization.NewService(...)
+    svc.SetMembershipService(mockMembership)
-    return svc, mockRepo
+    return svc, mockRepo, mockMembership
   }

+  t.Run("filters by principal orgs", func(t *testing.T) {
+    svc, mockRepo, mockMembership := newService()
+    p := authenticate.Principal{ID: "u1", Type: schema.UserPrincipal}
+    mockMembership.EXPECT().ListOrgsByPrincipal(mock.Anything, p).Return([]string{"org-1", "org-2"}, nil).Once()
+    mockRepo.On("List", ctx, organization.Filter{Principal: &p, IDs: []string{"org-1", "org-2"}}).
+      Return([]organization.Organization{{ID: "org-1"}, {ID: "org-2"}}, nil).Once()
+    _, err := svc.List(ctx, organization.Filter{Principal: &p})
+    assert.NoError(t, err)
+  })
+
+  t.Run("returns empty when principal has no matching orgs", func(t *testing.T) {
+    svc, _, mockMembership := newService()
+    p := authenticate.Principal{ID: "u1", Type: schema.UserPrincipal}
+    mockMembership.EXPECT().ListOrgsByPrincipal(mock.Anything, p).Return([]string{}, nil).Once()
+    got, err := svc.List(ctx, organization.Filter{Principal: &p})
+    assert.NoError(t, err)
+    assert.Empty(t, got)
+  })
 }
core/deleter/service_test.go (1)

263-264: ⚡ Quick win

Tighten the membership expectation to assert principal and filter shape.

Using mock.Anything for principal/filter makes this test pass even if DeleteUser sends wrong principal type/ID or non-empty filter.

Proposed tightening
-		mbrSvc.EXPECT().ListResourcesByPrincipal(mock.Anything, mock.Anything, schema.OrganizationNamespace, mock.Anything).
+		mbrSvc.EXPECT().ListResourcesByPrincipal(
+			mock.Anything,
+			authenticate.Principal{ID: "user-1", Type: schema.UserPrincipal},
+			schema.OrganizationNamespace,
+			membership.ResourceFilter{},
+		).
 			Return(nil, nil)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b5a1bbdc-3384-4d90-a664-3ab1a3962789

📥 Commits

Reviewing files that changed from the base of the PR and between 98d6246 and d89cab4.

📒 Files selected for processing (26)
  • core/deleter/mocks/membership_service.go
  • core/deleter/mocks/organization_service.go
  • core/deleter/service.go
  • core/deleter/service_test.go
  • core/domain/mocks/membership_service.go
  • core/domain/mocks/org_service.go
  • core/domain/mocks/repository.go
  • core/domain/mocks/user_service.go
  • core/domain/service.go
  • core/domain/service_test.go
  • core/invitation/mocks/membership_service.go
  • core/invitation/mocks/organization_service.go
  • core/invitation/service.go
  • core/invitation/service_test.go
  • core/membership/service.go
  • core/organization/filter.go
  • core/organization/mocks/membership_service.go
  • core/organization/mocks/relation_service.go
  • core/organization/service.go
  • core/organization/service_test.go
  • internal/api/v1beta1connect/authenticate.go
  • internal/api/v1beta1connect/interfaces.go
  • internal/api/v1beta1connect/mocks/organization_service.go
  • internal/api/v1beta1connect/organization.go
  • internal/api/v1beta1connect/user.go
  • internal/api/v1beta1connect/user_test.go
💤 Files with no reviewable changes (4)
  • core/organization/mocks/relation_service.go
  • internal/api/v1beta1connect/interfaces.go
  • core/invitation/mocks/organization_service.go
  • core/deleter/mocks/organization_service.go

Comment thread core/domain/service_test.go Outdated
@coveralls
Copy link
Copy Markdown

coveralls commented May 21, 2026

Coverage Report for CI Build 26277922087

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.08%) to 42.675%

Details

  • Coverage increased (+0.08%) from the base build.
  • Patch coverage: 40 uncovered changes across 5 files (35 of 75 lines covered, 46.67%).
  • 2 coverage regressions across 2 files.

Uncovered Changes

File Changed Covered %
core/organization/service.go 18 2 11.11%
internal/api/v1beta1connect/organization.go 27 15 55.56%
internal/api/v1beta1connect/authenticate.go 7 0 0.0%
core/membership/service.go 3 0 0.0%
core/deleter/service.go 5 3 60.0%

Coverage Regressions

2 previously-covered lines in 2 files lost coverage.

File Lines Losing Coverage Coverage
core/deleter/service.go 1 51.8%
internal/api/v1beta1connect/organization.go 1 64.58%

Coverage Stats

Coverage Status
Relevant Lines: 37905
Covered Lines: 16176
Line Coverage: 42.68%
Coverage Strength: 11.94 hits per line

💛 - Coveralls

@AmanGIT07
Copy link
Copy Markdown
Contributor Author

End-to-end test results

Ran the matrix against a local server built from this branch. Fixtures (users + orgs) were created fresh per run — no pre-existing data was relied on for assertions.

Setup

  • 5 orgs: A, B enabled (alice owner); C admin-only; D disabled (alice retains stale owner policy); E admin-only with verified test.pixxel.co.in domain.
  • Users: alice, bob, nomem (no policies), invitee, plus per-section fresh users (delu, dou, frank, derive, eve, su-cred).

Results

# Section Case Expected Actual Status
1 ListOrganizations no user_id, default state A,B,C,E in; D out 200, contains A,B,C,E, excludes D PASS
2 ListOrganizations state=disabled D in contains D, excludes others PASS
3 ListOrganizations user_id=alice (default) A,B in; C,D out 200, ids=A,B PASS
4 ListOrganizations user_id=alice + state=enabled A,B in 200, ids=A,B PASS
5 ListOrganizations user_id=alice + state=disabled D in 200, ids=D PASS
6 ListOrganizations user_id=user with 0 memberships empty 200, empty PASS
7 ListOrganizations user_id=random UUID empty (not error) 200, empty PASS
8 ListOrganizations pagination page_size=2 exactly 2 returned len=2 PASS
9 ListAllOrganizations no user_id, default A,B,C,E in; D out 200, count=742 PASS
10 ListAllOrganizations user_id=alice default A,B in 200, count=2 PASS
11 ListAllOrganizations user_id=alice state=disabled D in 200, count=1 PASS
12 ListAllOrganizations count field present count present present PASS
13 ListOrganizationsByUser alice happy path (default) A,B in; D out 200, ids=A,B; joinable=∅ PASS
14 ListOrganizationsByUser alice state=disabled D in 200, ids=D PASS
15 ListOrganizationsByUser user with no memberships empty 200, empty PASS
16 ListOrganizationsByUser user does not exist CodeNotFound 404 user doesn't exist PASS
17 ListOrganizationsByCurrentUser alice (default) A,B in; D out 200, ids=A,B PASS
18 ListOrganizationsByCurrentUser alice state=disabled D in 200, ids=D PASS
19 ListOrganizationsByCurrentUser nomem as current user empty 200, empty PASS
20 ListOrganizationsByCurrentUser unauthenticated 401/CodeUnauthenticated 401 PASS
21 AuthToken (orgs claim) alice JWT: D excluded A,B in; D out orgs=A,B PASS
22 AuthToken (orgs claim) nomem JWT orgs empty empty empty PASS
23 AuthToken (orgs claim) regular user, only-disabled-org 200 + empty orgs claim 200, orgs='' PASS
24 AuthToken (orgs claim) service user, disabled org failed_precondition, "org is disabled" matched (per the recent SU-disabled-org fix) PASS
25 DeleteUser (behavior change) cleans enabled + disabled org policies del=200; 0 enabled + 0 disabled org policies left matched (no orphan rows in policies table) PASS
26 DeleteUser nonexistent UUID CodeNotFound 404 PASS
27 CreateOrganizationInvitation new invite to enabled org 200 200 PASS
28 CreateOrganizationInvitation invite already-member ErrAlreadyMember 409 user is already member PASS
29 CreateOrganizationInvitation (behavior change) invite alice (stale policy) to disabled D err (already_member or disabled) 400 org is disabled PASS
30 CreateOrganizationInvitation retry existing invite 200 (idempotent) 200 PASS
31 AcceptOrganizationInvitation accept new invite 200, becomes member 200 PASS
32 AcceptOrganizationInvitation post-accept membership check invitee in org A confirmed PASS
33 AcceptOrganizationInvitation idempotent accept (already member via other flow) 200/409 200 PASS
34 AcceptOrganizationInvitation accept invite to disabled org err (ErrDisabled) 400 org is disabled PASS
35 Domain join alice sees E joinable via verified domain E in joinable_via_domain matched PASS
36 Domain join alice does NOT see A as joinable (already member) A excluded matched PASS
37 Domain join (behavior change) alice w/ stale policy on disabled D: D not joinable D excluded matched PASS

Summary

Section Total Pass
ListOrganizations 8 8
ListAllOrganizations 4 4
ListOrganizationsByUser 4 4
ListOrganizationsByCurrentUser 4 4
AuthToken (orgs claim) 4 4
DeleteUser 2 2
CreateOrganizationInvitation 4 4
AcceptOrganizationInvitation 4 4
Domain join 3 3
Total 37 37

Key behavior-change confirmations

  • DeleteUser (test 25): no policies rows remain for the deleted user, including for the disabled org D — confirms the membership-direct path correctly visits disabled orgs.
  • CreateOrganizationInvitation (test 29): re-invite to a disabled org with a stale-policy user now blocks with failed_precondition.
  • JWT orgs claim (test 21): disabled D is dropped via the explicit State: organization.Enabled filter in authenticate.go.
  • Domain join (test 37): ListJoinableOrgsByDomain correctly recognizes alice as already-member of stale-disabled D via the state-agnostic membership read, so D is excluded from joinable_via_domain.

Gaps from the PR's "RPCs to verify" list

Not run yet:

  • ListOrganizationsByCurrentUser as service user
  • ListOrganizationsByCurrentUser as PAT (PAT must yield intersected set)
  • AuthToken JWT orgs claim for PAT principal (PAT-narrowed)
  • Manual stale-relation regression: demote an owner to viewer, confirm they no longer appear in the listing APIs

Adjacent finding (not introduced here)

While running test 37 I tripped a pre-existing 500 in the joinable-by-domain path for a different shape: user has no policy on the disabled org (so the new membership filter can't help). Filed separately as #1638. Reproduces on main too.

Artifacts

Test harness lives under ~/frontier-test/tests/ (lib.sh, setup_fixtures.sh, run_matrix.sh); results in results.tsv.

@AmanGIT07
Copy link
Copy Markdown
Contributor Author

Gap tests — follow-up

Ran the four cases left over from the previous comment.

# Section Case Setup Expected Actual Status
38 ListOrganizationsByCurrentUser as service user SU created under ORG_A, client_credentials Basic auth 200, organizations=[A], joinable_via_domain absent (SU path skips domain-join) 200, organizations=[A], no joinable PASS
39 ListOrganizationsByCurrentUser as PAT (narrowed) PAT for alice scoped {role: viewer, resource_type: app/organization} (i.e. all orgs in ORG_A — actually grants ORG_A only because that's the PAT's org_id); alice also owns B & D 200, organizations=[A] only (PAT narrows the principal even though alice has policies on B and D) 200, organizations=[A] PASS
40 AuthToken orgs claim PAT principal, JWT claim narrowed Same PAT as #39, AuthToken via Authorization: Bearer <pat> JWT org_ids claim = ORG_A org_ids = ORG_A PASS
41a Stale-relation regression add member, listing reflects it AdminService.AddOrganizationMembers adds bob to ORG_A as manager Bob's ListOrganizationsByCurrentUser shows A; policies row exists with manager role matched PASS
41b Stale-relation regression demote manager → viewer SetOrganizationMemberRole(viewer) Bob still appears in listing (viewer is still a policy → still a member); DB shows exactly one policy with viewer role matched PASS
41c Stale-relation regression remove member RemoveOrganizationMember(principal_type=app/user) Bob no longer appears in his listing; DB has 0 policies for bob on ORG_A matched PASS

Notes

  • PAT narrowing works as designed. Alice has policies on A, B, D — without the PAT she'd see all three (with state filter). The PAT-token request returns only ORG_A, both in the listing endpoint and in the JWT org_ids claim. This confirms the organization.Service.List path correctly intersects the principal's full-membership set against the PAT's org_id (via membership.ListOrgsByPrincipal which calls listOrgsForPrincipal and intersects when principal.PAT != nil).
  • Service-user listing: SU has no email-domain → principal.Type == ServiceUserPrincipal branch in user.go correctly skips the joinable-by-domain block, response carries no joinable_via_domain field at all.
  • Demote vs remove distinction holds. A member with a viewer-role policy still counts as a member under the new Postgres-policies path (no role-level filtering), but a RemoveOrganizationMember call wipes the policy and the user immediately disappears from listings. This is the correct semantic — the previous SpiceDB-based path had occasional cache-staleness issues here, the new path is fully transactional.

Updated section totals

Section Total Pass
Original 9 sections 37 37
Gap follow-up 6 6
Grand total 43 43

All items from the PR's "RPCs to verify end-to-end" checklist are now covered.

Mock cleanup and expectation assertions were attached to the outer
test instead of each subtest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AmanGIT07 AmanGIT07 enabled auto-merge (squash) May 22, 2026 07:32
@AmanGIT07 AmanGIT07 disabled auto-merge May 22, 2026 07:32
@AmanGIT07 AmanGIT07 enabled auto-merge (squash) May 22, 2026 07:32
Satisfies the thelper lint after taking *testing.T as a parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AmanGIT07 AmanGIT07 merged commit 592ede2 into main May 22, 2026
8 checks passed
@AmanGIT07 AmanGIT07 deleted the feature/orglistbyuser-migrate-callers-to-membership-and-delete branch May 22, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants